#include "BackupFSSql.h"
#include "../sqlite/sqlite3.h"
#include "../logger/BackupFSLogger.h"
#include "BackupFSFileNode.h"
namespace BackupFS
{
	std::recursive_mutex BackupFSSql::_commit_mutex;
	bool BackupFSSql::AddFileMeta(void* vdb, const std::shared_ptr<BackupFSFileNode>& file)
	{
		/*
		id
		name
		is_folder
		parent_id
		file_size
		attributes
		create_time
		modify_time
		access_time
		security
		*/
		sqlite3* db = (sqlite3*)vdb;
		std::string sql = "INSERT INTO files (id,name,is_folder,parent_id,file_size,attributes,create_time,"
			"modify_time,access_time,security) VALUES (?,?,?,?,?,?,?,?,?,?);";
		sqlite3_stmt* stmt;
		int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
		if (rc != SQLITE_OK)
		{
			return false;
		}
		int i = 1;
		std::string file_name = W2S(file->GetFileName());
		sqlite3_bind_int64(stmt, i++, file->NodeId);
		sqlite3_bind_text(stmt, i++, file_name.c_str(), -1, NULL);
		sqlite3_bind_int(stmt, i++, file->IsDirectory ? 1 : 0);
		sqlite3_bind_int64(stmt, i++, file->ParentId);
		sqlite3_bind_int64(stmt, i++, file->GetFileSize());
		sqlite3_bind_int(stmt, i++, file->Attributes);
		sqlite3_bind_int64(stmt, i++, file->Times.Creation);
		sqlite3_bind_int64(stmt, i++, file->Times.LastWrite);
		sqlite3_bind_int64(stmt, i++, file->Times.LastAccess);
		if (file->Security.DescriptorSize > 0)
		{
			sqlite3_bind_blob(stmt, i++, file->Security.Descriptor.get(), file->Security.DescriptorSize, NULL);
		}
		else
		{
			sqlite3_bind_null(stmt, i++);
		}
		rc = sqlite3_step(stmt);
		sqlite3_finalize(stmt);
		return UpdateCurrentNodeId(vdb, file->NodeId + 1);
	}
	bool BackupFSSql::AddDataBlock(void* vdb, const std::shared_ptr<DATA_BLOCK>& block, LONGLONG node_id)
	{
		sqlite3* db = (sqlite3*)vdb;
		std::string sql = "INSERT INTO data_blocks (id,file_id,offset_in_file,offset_in_data,length,attr) "
			"VALUES (?,?,?,?,?,?);";
		sqlite3_stmt* stmt;
		int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
		if (rc != SQLITE_OK)
		{
			return false;
		}
		int i = 1;
		sqlite3_bind_int64(stmt, i++, block->Id);
		sqlite3_bind_int64(stmt, i++, node_id);
		sqlite3_bind_int64(stmt, i++, block->OffsetInFile);
		sqlite3_bind_int64(stmt, i++, block->OffsetInData);
		sqlite3_bind_int64(stmt, i++, block->Length);
		sqlite3_bind_int(stmt, i++, (int)block->Attr);
		rc = sqlite3_step(stmt);
		sqlite3_finalize(stmt);
		return UpdateCurrentBlockId(vdb,block->Id + 1);
	}
	bool BackupFSSql::UpdateFileTime(void* vdb, LONGLONG node_id, LONGLONG create_time, LONGLONG write_time, LONGLONG access_time)
	{
		sqlite3* db = (sqlite3*)vdb;
		std::string sql = "Update files set create_time=?,modify_time=?,access_time=? where id=?;";
		sqlite3_stmt* stmt;
		int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
		if (rc != SQLITE_OK)
		{
			return false;
		}
		int i = 1;
		sqlite3_bind_int64(stmt, i++, create_time);
		sqlite3_bind_int64(stmt, i++, write_time);
		sqlite3_bind_int64(stmt, i++, access_time);
		sqlite3_bind_int64(stmt, i++, node_id);
		rc = sqlite3_step(stmt);

		sqlite3_finalize(stmt);

		return (rc == SQLITE_DONE);
	}
	bool BackupFSSql::UpdateFileSecurity(void* vdb, LONGLONG node_id,byte* data, LONGLONG size)
	{
		sqlite3* db = (sqlite3*)vdb;
		std::string sql = "Update files set security=? where id=?;";
		sqlite3_stmt* stmt;
		int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
		if (rc != SQLITE_OK)
		{
			return false;
		}
		int i = 1;
		if (size > 0)
		{
			sqlite3_bind_blob(stmt, i++, data, (int)size, NULL);
		}
		else
		{
			sqlite3_bind_null(stmt, i++);
		}
		sqlite3_bind_int64(stmt, i++, node_id);
		rc = sqlite3_step(stmt);

		sqlite3_finalize(stmt);

		return (rc == SQLITE_DONE);
	}
	bool BackupFSSql::GetFsInfo(void* vdb, FS_INFO& fsinfo)
	{
		sqlite3* db = (sqlite3*)vdb;
		std::string sql = "select current_node_id,current_block_id,size,used_size from fsinfo;";

		sqlite3_stmt* stmt;
		int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr);
		if (rc != SQLITE_OK)
		{
			return false;
		}
		if ((rc = sqlite3_step(stmt)) == SQLITE_ROW)
		{
			int i = 0;
			fsinfo.CurrentNodeId = sqlite3_column_int64(stmt, i++);
			fsinfo.CurrentBlockId = sqlite3_column_int64(stmt, i++);
			fsinfo.Size = sqlite3_column_int64(stmt, i++);
			fsinfo.UsedSize = sqlite3_column_int64(stmt, i++);
		}
		else
		{
			fsinfo.CurrentNodeId = 2;
			fsinfo.CurrentBlockId = 1;
			fsinfo.Size = 0;
			fsinfo.UsedSize = 0;
		}
		sqlite3_finalize(stmt);
		return true;
	}
	bool BackupFSSql::_UpdateFsINFO(void* vdb, const std::string& field_name, LONGLONG val)
	{
		sqlite3* db = (sqlite3*)vdb;
		std::string sql = "Update fsinfo set " + field_name + " = ? where id = 1; ";
		sqlite3_stmt* stmt;
		int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
		if (rc != SQLITE_OK)
		{
			return false;
		}
		sqlite3_bind_int64(stmt, 1, val);
		rc = sqlite3_step(stmt);
		
		sqlite3_finalize(stmt);

		return (rc == SQLITE_DONE);
	}
	bool BackupFSSql::UpdateCurrentNodeId(void* vdb, LONGLONG node_id)
	{
		return _UpdateFsINFO(vdb, "current_node_id", node_id);
	}
	bool BackupFSSql::UpdateCurrentBlockId(void* vdb, LONGLONG block_id)
	{
		return _UpdateFsINFO(vdb, "current_block_id", block_id);
	}
	bool BackupFSSql::UpdateCurrentFSSize(void* vdb, LONGLONG size)
	{
		return _UpdateFsINFO(vdb, "size", size);
	}
	bool BackupFSSql::UpdateCurrentUsedSize(void* vdb, LONGLONG size)
	{
		return _UpdateFsINFO(vdb, "used_size", size);
	}
	bool BackupFSSql::UpdateDataBlock(void* vdb, const std::shared_ptr<DATA_BLOCK>& block)
	{
		sqlite3* db = (sqlite3*)vdb;
		std::string sql = "Update data_blocks set offset_in_data=?,offset_in_file=?,length=? where id=?;";
		sqlite3_stmt* stmt;
		int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
		if (rc != SQLITE_OK)
		{
			return false;
		}
		int i = 1;
		sqlite3_bind_int64(stmt, i++, block->OffsetInData);
		sqlite3_bind_int64(stmt, i++, block->OffsetInFile);
		sqlite3_bind_int64(stmt, i++, block->Length);
		sqlite3_bind_int64(stmt, i++, block->Id);
		rc = sqlite3_step(stmt);

		sqlite3_finalize(stmt);

		return (rc == SQLITE_DONE);
	}
	bool BackupFSSql::RemoveFile(void* vdb, const std::shared_ptr<BackupFSFileNode>& file)
	{
		sqlite3* db = (sqlite3*)vdb;
		char* err_msg = 0;
		std::string sql = "delete from files where id=" + std::to_string(file->NodeId) + ";";
		int rc = sqlite3_exec(db, sql.c_str(), NULL, 0, &err_msg);
		if (rc != SQLITE_OK)
		{
			if (err_msg)
			{
				sqlite3_free(err_msg);
			}
			return false;
		}
		// if file is not file
		if (file->IsDirectory)
		{
			return true;
		}
		sql = "delete from data_blocks where file_id=" + std::to_string(file->NodeId) + ";";
		rc = sqlite3_exec(db, sql.c_str(), NULL, 0, &err_msg);
		if (rc != SQLITE_OK)
		{
			if (err_msg)
			{
				sqlite3_free(err_msg);
			}
			return false;
		}
		return true;
	}
	bool BackupFSSql::UpdateChildrenFileName(void* vdb, const std::wstring& old_file_name, const std::wstring& new_file_name)
	{
		sqlite3* db = (sqlite3*)vdb;

		std::string sql = "UPDATE files SET name = ? || SUBSTR(name, LENGTH(?) ) WHERE name LIKE ?;";
		sqlite3_stmt* stmt;
		int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
		if (rc != SQLITE_OK)
		{
			return false;
		}
		std::string new_name = W2S(new_file_name)+"\\";
		std::string old_name = W2S(old_file_name) + "\\%";
		int i = 1;
		sqlite3_bind_text(stmt, i++, new_name.c_str(), -1, NULL);
		sqlite3_bind_text(stmt, i++, old_name.c_str(), -1, NULL);
		sqlite3_bind_text(stmt, i++, old_name.c_str(), -1, NULL);

		rc = sqlite3_step(stmt);

		sqlite3_finalize(stmt);

		return (rc == SQLITE_DONE);
	}
	bool BackupFSSql::UpdateFileName(void* vdb, LONGLONG node_id, const std::wstring& file_name, LONGLONG parent_id)
	{
		sqlite3* db = (sqlite3*)vdb;
		std::string sql = "Update files set name=?,parent_id=? where id=?;";
		sqlite3_stmt* stmt;
		int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
		if (rc != SQLITE_OK)
		{
			return false;
		}
		std::string name = W2S(file_name);
		int i = 1;
		sqlite3_bind_text(stmt, i++, name.c_str(), -1, NULL);
		sqlite3_bind_int64(stmt, i++, parent_id);
		sqlite3_bind_int64(stmt, i++, node_id);
		rc = sqlite3_step(stmt);
		sqlite3_finalize(stmt);
		return (rc == SQLITE_DONE);
	}
	bool BackupFSSql::UpdateFileSize(void* vdb, LONGLONG node_id, LONGLONG file_size)
	{
		sqlite3* db = (sqlite3*)vdb;
		std::string sql = "Update files set file_size=? where id=?;";
		sqlite3_stmt* stmt;
		int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
		if (rc != SQLITE_OK)
		{
			return false;
		}
		int i = 1;
		sqlite3_bind_int64(stmt, i++, file_size);
		sqlite3_bind_int64(stmt, i++, node_id);
		rc = sqlite3_step(stmt);
		
		sqlite3_finalize(stmt);

		return (rc == SQLITE_DONE);
	}

	std::shared_ptr<BackupFSFileNode> BackupFSSql::GetFileNode(void* vdb, const std::wstring& file_name)
	{
		sqlite3* db = (sqlite3*)vdb;
		std::string sql = "select id,is_folder,parent_id,file_size,attributes,create_time,modify_time,access_time from files where name=?";
		std::string name = W2S(file_name);
		sqlite3_stmt* stmt;
		int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr);
		if (rc != SQLITE_OK)
		{
			return nullptr;
		}
		sqlite3_bind_text(stmt, 1, name.c_str(), -1, NULL);
		rc = sqlite3_step(stmt);
		std::shared_ptr<BackupFSFileNode> file = nullptr;
		if (rc == SQLITE_ROW)
		{
			//
			/*
			id
			name
			is_folder
			parent_id
			file_size
			attributes
			create_time
			modify_time
			access_time
			security
			*/
			int i = 0;
			LONGLONG id,is_folder,parent_id,file_size,attributes,create_time,modify_time,access_time = 0;
			id = sqlite3_column_int64(stmt, i++);
			is_folder = sqlite3_column_int64(stmt, i++);
			parent_id = sqlite3_column_int64(stmt, i++);
			file_size = sqlite3_column_int64(stmt, i++);
			attributes = sqlite3_column_int64(stmt, i++);
			create_time = sqlite3_column_int64(stmt, i++);
			modify_time = sqlite3_column_int64(stmt, i++);
			access_time = sqlite3_column_int64(stmt, i++);
			file = std::make_shared<BackupFSFileNode>(file_name, is_folder > 0 ? true : false, (DWORD)attributes, nullptr);
			file->NodeId = id;
			file->ParentId = parent_id;
			file->Times.Creation = create_time;
			file->Times.LastWrite = modify_time;
			file->Times.LastAccess = access_time;
			file->FileSize = file_size;
			//file size is <= alocated size
			file->SetAllocatedSize(ROUND_UP_BLOCK_SIZE(file_size));
		}
		sqlite3_finalize(stmt);
		return file;
	}
	bool BackupFSSql::GetAllDataBlocks(void* vdb, LONGLONG file_id, std::list<std::shared_ptr<DATA_BLOCK>>& blocks)
	{
		/*
		id
		file_id
		offset
		length
		data_length
		*/
		sqlite3* db = (sqlite3*)vdb;
		std::string sql = "select id,offset_in_file,offset_in_data,length,attr from data_blocks where file_id=? order by id asc;";
		
		sqlite3_stmt* stmt;
		int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr);
		if (rc != SQLITE_OK)
		{
			return false;
		}
		sqlite3_bind_int64(stmt, 1, file_id);
		while ((rc = sqlite3_step(stmt)) == SQLITE_ROW)
		{
			std::shared_ptr<DATA_BLOCK> block = std::make_shared<DATA_BLOCK>();
			int i = 0;
			block->Id = sqlite3_column_int64(stmt, i++);
			block->OffsetInFile = sqlite3_column_int64(stmt, i++);
			block->OffsetInData = sqlite3_column_int64(stmt, i++);
			block->Length = sqlite3_column_int64(stmt, i++);
			block->Attr = (DATA_ATTR)sqlite3_column_int(stmt, i++);
			blocks.push_back(block);
		}

		sqlite3_finalize(stmt);
		return true;
	}
	bool BackupFSSql::GetAllChildren(void* vdb, const std::wstring file_name, std::set <std::shared_ptr<BackupFSFileNode>>& files)
	{
		sqlite3* db = (sqlite3*)vdb;
		std::string sql = "select id,name,is_folder,parent_id,file_size,attributes,create_time,modify_time,access_time,security from files where parent_id=(select id from files where name=?) order by id asc;";
		std::string name = W2S(file_name);
		sqlite3_stmt* stmt;
		int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr);
		if (rc != SQLITE_OK)
		{
			return false;
		}
		sqlite3_bind_text(stmt, 1, name.c_str(), -1, NULL);
		while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) 
		{
			//
			/*
			id
			name
			is_folder
			parent_id
			file_size
			attributes
			create_time
			modify_time
			access_time
			security
			*/
			int i = 0;
			LONGLONG id, is_folder, parent_id, file_size, attributes, create_time, modify_time, access_time = 0;
			id = sqlite3_column_int64(stmt, i++);
			std::string name = std::string(reinterpret_cast<const char*>(sqlite3_column_text(stmt, i++)));
			is_folder = sqlite3_column_int64(stmt, i++);
			parent_id = sqlite3_column_int64(stmt, i++);
			file_size = sqlite3_column_int64(stmt, i++);
			attributes = sqlite3_column_int64(stmt, i++);
			create_time = sqlite3_column_int64(stmt, i++);
			modify_time = sqlite3_column_int64(stmt, i++);
			access_time = sqlite3_column_int64(stmt, i++);

			std::shared_ptr<BackupFSFileNode> file = std::make_shared<BackupFSFileNode>(S2W(name), is_folder > 0 ? true : false, (DWORD)attributes, nullptr);
			file->NodeId = id;
			file->ParentId = parent_id;
			file->Times.Creation = create_time;
			file->Times.LastWrite = modify_time;
			file->Times.LastAccess = access_time;
			file->FileSize = file_size;

			//file size is <= alocated size
			file->SetAllocatedSize(ROUND_UP_BLOCK_SIZE(file_size));
			file->Security.DescriptorSize = sqlite3_column_bytes(stmt, i);
			if (file->Security.DescriptorSize > 0)
			{
				file->Security.Descriptor = std::make_unique<byte[]>(file->Security.DescriptorSize);
				memcpy(file->Security.Descriptor.get(), sqlite3_column_blob(stmt, i), file->Security.DescriptorSize);
			}
			if (!file->IsDirectory)
			{
				GetAllDataBlocks(vdb, file->NodeId, file->GetDataBlocks());
			}
			files.insert(file);
		}
		sqlite3_finalize(stmt);
		return true;
	}
	bool BackupFSSql::Begin(void* vdb)
	{
		sqlite3* db = (sqlite3*)vdb;
		int rc = sqlite3_exec(db, "begin;", 0, 0, 0);
		return  (rc == SQLITE_OK);
	}
	bool BackupFSSql::Commit(void* vdb)
	{
		std::scoped_lock lock(_commit_mutex);
		sqlite3* db = (sqlite3*)vdb;
		int rc = sqlite3_exec(db, "commit;", 0, 0, 0);
		return  (rc == SQLITE_OK);
	}
	void BackupFSSql::Stop(void* vdb)
	{
		BackupFSSql::Commit(vdb);
		sqlite3* db = (sqlite3*)vdb;
		sqlite3_close_v2(db);
		Logger->Info("Close DB");
	}
}